// RTTI 意为 运行时类型识别, 只能用在包含虚函数的类继承环境
// 包含3个部分
// 1. dynamic_cast运算符
// dynamic_cast<typename> (pt);  用于能否将pt转化成一个<typename>类型的指针,能-> 返回转换完成的类型的指针,  不能-> 返回0(即为NULL)
//
#include <iostream>
#include <cstdlib>
#include <ctime>

using std::cout;

// 声明了三个类,分别使用继承关系
class Grand
{
private:
    int hold;

public:
    Grand(int h = 0) : hold(h) {}
    virtual void Speak() const { cout << "I am a grand class!\n"; }
    virtual int Value() const { return hold; }
};

class Superb : public Grand
{
public:
    Superb(int h = 0) : Grand(h) {}
    void Speak() const { cout << "I am a superb class!!\n"; }
    virtual void Say() const
    {
        cout << "I hold the superb value of " << Value() << "!\n";
    }
};

class Magnificent : public Superb
{
private:
    char ch;

public:
    Magnificent(int h = 0, char c = 'A') : Superb(h), ch(c) {}
    void Speak() const { cout << "I am a magnificent class!!!\n"; }
    void Say() const { cout << "I hold the character " << ch << " and the integer " << Value() << "!\n"; }
};
// 这个函数返回一个Grand(父类)实例,但是通过随机函数生成上面3个类的派生对象,最终付给Grand类指针
Grand *GetOne();

int main()
{
    std::srand(std::time(0));
    Grand *pg;
    Superb *ps;
    for (int i = 0; i < 5; i++)
    {
        pg = GetOne();
        pg->Speak();
        // 核心代码
        if (ps = dynamic_cast<Superb *>(pg)) // 如果pg可以安全的转换成Superb类型,那么将会执行Say()函数
            ps->Say();
    }
    // std::cin.get();
    return 0;
}

Grand *GetOne() // generate one of three kinds of objects randomly
{
    Grand *p;
    switch (std::rand() % 3)
    {
    case 0:
        p = new Grand(std::rand() % 100);
        break;
    case 1:
        p = new Superb(std::rand() % 100);
        break;
    case 2:
        p = new Magnificent(std::rand() % 100,
                            'A' + std::rand() % 26);
        break;
    }
    return p;
}

// dynamic_cast用于引用类型
// 也可以将dynamic_cast用于引用对象,只是用法有一点不同: 没有与空指针对应的引用值,因此无法使用特殊的引用值来表示失败,所以,
// 需要指出的是: dynamic_cast<>() 中()里面的内容如果不能访问或者为NULL(请求不正确),将会引发名为bad_cast的异常,bad_cast异常是从exception派生而来,定义在typeinfo文件中.

// 2. typeid运算符
// typeid运算符返回一个type_info对象的引用
// type_info类重载了==和!=运算符,用于比较两个类对象
// typeid(Magnificent) == typeid(*pg)  // 这里pg是一个类对象的指针,如果pg值一个空指针,那么将会引发bad_typeid异常. bad_typeid由exception派生而来,头文件在typeinfo文件中声明
// 如果放弃使用上面使用的dynamic_cast运算符,应该如下使用代码
Grand *GetOne();
int main()
{
    srand(time(0));
    Grand *pg;
    Superb *ps;
    for (int i = 0; i < 5; i++)
    {
        pg = GetOne();
        cout << "Now processing type " << typeid(*pg).name() << ".\n";
        pg->Speak();
        if (ps = dynamic_cast<Superb *>(pg))
            ps->Say();
        if (typeid(Magnificent) == typeid(*pg))
            cout << "Yes, you're really magnificent.\n";
    }
    // std::cin.get();
    return 0;
}
// 注意: 使用typeid用来判定类型将会使程序失去扩展性! 因为使用typeid判定每一个类型的时候,
// 如果这个时候新增了一个派生类型,那么你将会修改每一个使用这种判定方式的函数
// 总之如果你发现了一个使用if else结构的用来判定类型的函数,那么可以考虑将typeid转换成dynamic_cast运算符

// 类型转换运算符
// 下面是4中类型转化运算符
// 1. dynamic_cast
// 2. const_cast
// 3. static_cast
// 4. reinterpret_cast

// 1. dynamic_cast 适用于用于能否将pt转化成一个<typename>类型的指针,能-> 返回转换完成的类型的指针,  不能-> 返回0(即为NULL)
// （1）其他三种都是编译时完成的，dynamic_cast是运行时处理的，运行时要进行类型检查。
// （2）不能用于内置的基本数据类型的强制转换。
// （3）dynamic_cast转换如果成功的话返回的是指向类的指针或引用，转换失败的话则会返回NULL。
// （4）使用dynamic_cast进行转换的，基类中一定要有虚函数，否则编译不通过。
//     B中需要检测有虚函数的原因：类中存在虚函数，就说明它有想要让基类指针或引用指向派生类对象的情况，此时转换才有意义。
//     这是由于运行时类型检查需要运行时类型信息，而这个信息存储在类的虚函数表（关于虚函数表的概念，详细可见<Inside c++ object model>）中，
//     只有定义了虚函数的类才有虚函数表。
//  （5）在类的转换时，在类层次间进行上行转换时，dynamic_cast和static_cast的效果是一样的。在进行下行转换时，dynamic_cast具有类型检查的功能，比static_cast更安全。
//     向上转换，即为子类指针指向父类指针（一般不会出问题）；向下转换，即将父类指针转化子类指针。
//     向下转换的成功与否还与将要转换的类型有关，即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同，否则转换失败。
//     在C++ 中，编译期的类型转换有可能会在运行时出现错误，特别是涉及到类对象的指针或引用操作时，更容易产生错误。Dynamic_cast操作符则可以在运行期对可能产生问题的类型转换进行测试。

// 2. const_cast运算符
// const_cast用于执行一种用途的类型转换，即改变类型为const或者volatile，语法和dynamic_cast相同
// 类似下面这样：
// Grand * pt;
// const_cast <const Grand *> (pt);
//
// 在C语言中，const限定符通常被用来限定变量，用于表示该变量的值不能被修改。
// 而const_cast则正是用于强制去掉这种不能被修改的常数特性，但需要特别注意的是const_cast不是用于去除变量的常量性，而是去除指向常数对象的指针或引用的常量性，其去除常量性的对象必须为指针或引用。
//
// 用法：const_cast<type_id>(expression)
// 该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外， type_id和expression的类型是一样的。
//  常量指针被转化成非常量指针，并且仍然指向原来的对象；
//  常量引用被转换成非常量引用，并且仍然指向原来的对象；常量对象被转换成非常量对象。

// [例3]一个错误的例子：
//      const int a = 10;
//      const int * p = &a;
//      *p = 20;                  //compile error
//      int b = const_cast<int>(a);  //compile error
// 在本例中出现了两个编译错误，第一个编译错误是*p因为具有常量性，其值是不能被修改的；另一处错误是const_cast强制转换对象必须为指针或引用，而例3中为一个变量，这是不允许的！

// #include<iostream>
// using namespace std;
//
// const int *Search(const int *a, int n, int val);
//
// int main()
// {
//     int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
//     int val = 5;
//     int *p;
//     p = const_cast<int *>(Search(a, 10, val));
//     if (p == NULL)
//         cout << "Not found the val in array a" << endl;
//     else
//         cout << "hvae found the val in array a and the val = " << *p << endl;
//     return 0;
// }
//
// const int *Search(const int *a, int n, int val)
// {
//     int i;
//     for (i = 0; i < n; i++)
//     {
//         if (a[i] == val)
//             return &a[i];
//     }
//     return NULL;
// }
// 我们定义了一个函数，用于在a数组中寻找val值，如果找到了就返回该值的地址，如果没有找到则返回NULL。
// 函数Search返回值是const指针，当我们在a数组中找到了val值的时候，我们会返回val的地址，最关键的是a数组在
// main函数中并不是const，因此即使我们去掉返回值的常量性有可能会造成a数组被修改，但是这也依然是安全的。

// 3. static_cast
// 在C++语言中static_cast用于数据类型的强制转换，强制将一种数据类型转换为另一种数据类型。
// 用法：static_cast <类型说明符> （变量或表达式）
// 它主要有如下几种用法：
// （1）用于类层次结构中基类和派生类之间指针或引用的转换 进行上行转换（把派生类的指针或引用转换成基类表示）是安全的
//     进行下行转换（把基类的指针或引用转换为派生类表示），由于没有动态类型检查，所以是不安全的
// （2）用于基本数据类型之间的转换，如把int转换成char。这种转换的安全也要开发人员来保证
// （3）把空指针转换成目标类型的空指针
// （4）把任何类型的表达式转换为void类型 注意：static_cast不能转换掉expression的const、volitale或者__unaligned属性。
//   static_cast : 可以实现C++ 中内置基本数据类型之间的相互转换。

//   如果涉及到类的话，static_cast只能在有相互联系的类型中进行相互转换,不一定包含虚函数。

// 4. reinterpret_cast
// 在C++ 语言中，reinterpret_cast主要有三种强制转换用途：改变指针或引用的类型、将指针或引用转换为一个足够长度的整形、将整型转换为指针或引用类型。

//         用法：reinterpret_cast<type_id>(expression)
//             type
//     - id必须是一个指针、引用、算术类型、函数指针或者成员指针。
//     它可以把一个指针转换成一个整数，也可以把一个整数转换成一个指针（先把一个指针转换成一个整数，在把该整数转换成原类型的指针，还可以得到原先的指针值）。
//     在使用reinterpret_cast强制转换过程仅仅只是比特位的拷贝，因此在使用过程中需要特别谨慎！

//     例7：
//     int *a = new int;
// double *d = reinterpret_cast<double *>(a);
// 在例7中，将整型指针通过reinterpret_cast强制转换成了双精度浮点型指针。
//     reinterpret_cast可以将指针或引用转换为一个足够长度的整形，此中的足够长度具体长度需要多少则取决于操作系统，如果是32位的操作系统，就需要4个字节及以上的整型，如果是64位的操作系统则需要8个字节及以上的整型。